<template>
  <div class="webgl-blackhole">
    <h1>WEBGL旋转黑洞</h1>
    <p>使用WebGL绘制旋转黑洞效果，展示如何使用片元着色器实现复杂的视觉效果。</p>

    <el-card class="demo-section">
      <template #header>
        <div class="card-header">
          <h3>旋转黑洞展示</h3>
        </div>
      </template>
      <div class="demo-content">
        <canvas ref="demoCanvas" width="800" height="400"></canvas>
      </div>
    </el-card>

    <el-card class="code-section">
      <template #header>
        <div class="card-header">
          <h3>示例代码</h3>
        </div>
      </template>
      <div class="code-content">
        <pre><code>
// 顶点着色器
attribute vec4 a_Position;
void main() {
  gl_Position = a_Position;
}

// 片元着色器
precision mediump float;
uniform vec2 u_Resolution;
uniform float u_Time;

void main() {
  vec2 fragCoord = gl_FragCoord.xy / u_Resolution;

  // 计算宽高比
  float aspect = u_Resolution.x / u_Resolution.y;

  // 声明衰减因子和其他变量
  float attenuation;        // 用于计算光线衰减
  vec2 resolution = u_Resolution;

  // 将坐标系转换到[-1,1]范围，并进行Y轴翻转和宽高比校正
  vec2 centeredCoords = vec2((fragCoord.x * 2.0 - 1.0) * aspect, (fragCoord.y * 2.0 - 1.0))/.7;

  // 创建倾斜效果的向量
  vec2 diagonalVector = vec2(-1.0, 1.0);

  // 以黑位中心点的坐标
  vec2 blackholeCenter = centeredCoords - 0.2 * diagonalVector;

  // 计算旋转坐标，应用透视畸变
  vec2 rotatedCoords = centeredCoords * mat2(
    1.0, 1.0,  // 基础旋转矩阵
    diagonalVector / (0.1 + 0.2 / dot(blackholeCenter, blackholeCenter)) // 透视畸变
  );

  float ii = 0.2;
  // 计算螺旋坐标，创造旋转效果
  vec2 spiralingCoords = rotatedCoords * mat2(
    cos(0.5 * log(attenuation = dot(rotatedCoords, rotatedCoords)) // 计算衰减
    + u_Time * ii // 时间动画
    + vec4(0.0, 33.0, 11.0, 0.0)) // 初始旋转角度
  ) / 0.2;

  // 初始化波浪累积值
  vec2 wavesCumulativeTotal = vec2(0.0);

  // 创建波浪效果
  for(int i = 0; i < 9; i++) {
    float iterValue = float(i) + 1.0;
    ii += 1.0;
    // 累积波浪值
    wavesCumulativeTotal += 1.0 + sin(spiralingCoords);
    // 扭曲坐标，创造动态波浪
    spiralingCoords += 0.7 * sin(spiralingCoords.yx * ii + u_Time) / ii + 0.5;
  }

  // 计算吸积盘半径
  float radius = length(
    sin(spiralingCoords / 0.3) * 0.4 +
    rotatedCoords * (3.0 + diagonalVector)
  );

  // 计算最终颜色
  vec4 outputColor = 1.0 - exp(-exp(rotatedCoords.x * vec4(.6, -.4, -1, 0))
                       / wavesCumulativeTotal.xyyx     // 应用波浪效果
                       / (2.0 + radius * radius / 4.0 - radius) // 吸积盘亮度
                       / (0.5 + 1.0 / attenuation)     // 中心暗度
                       / (0.03 + abs(length(centeredCoords) - 0.6)) // 边缘高光
                      );

  // 输出最终颜色
  gl_FragColor = outputColor;
}
        </code></pre>
      </div>
    </el-card>

    <el-card class="info-section">
      <template #header>
        <div class="card-header">
          <h3>实现说明</h3>
        </div>
      </template>
      <el-descriptions :column="1" border>
        <el-descriptions-item label="绘制方式">
          使用片元着色器实现复杂的黑洞效果，通过数学计算模拟黑洞的引力和光线扭曲
        </el-descriptions-item>
        <el-descriptions-item label="动画效果">
          通过时间变量实现黑洞的旋转效果，模拟吸积盘的动态变化
        </el-descriptions-item>
        <el-descriptions-item label="参数控制">
          支持调整黑洞的旋转速度、大小和波浪强度，实时更新视觉效果
        </el-descriptions-item>
      </el-descriptions>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { WebGLUtils } from '@/utils/webgl'

const demoCanvas = ref<HTMLCanvasElement | null>(null)
const gl = ref<WebGLRenderingContext | null>(null)
const shaderProgram = ref<WebGLProgram | null>(null)
const webglUtils = ref<WebGLUtils | null>(null)
const animationFrameId = ref<number | null>(null)

// 时间变量
let startTime = 0

// 初始化顶点缓冲区
const initVertexBuffers = () => {
  if (!gl.value || !shaderProgram.value || !webglUtils.value) return false

  // 创建覆盖整个画布的矩形（两个三角形）
  const vertices = new Float32Array([
    -1.0, 1.0,   // 左上
    -1.0, -1.0,  // 左下
    1.0, 1.0,    // 右上
    1.0, -1.0    // 右下
  ])

  // 创建缓冲区
  const vertexBuffer = webglUtils.value.createBuffer(vertices)
  if (!vertexBuffer) return false

  // 获取着色器中的属性位置
  const a_Position = gl.value.getAttribLocation(shaderProgram.value, 'a_Position')

  // 设置顶点属性指针
  const FSIZE = vertices.BYTES_PER_ELEMENT
  gl.value.vertexAttribPointer(a_Position, 2, gl.value.FLOAT, false, FSIZE * 2, 0)
  gl.value.enableVertexAttribArray(a_Position)

  return true
}

// 绘制函数
const draw = () => {
  if (!gl.value || !shaderProgram.value || !demoCanvas.value) return

  // 计算当前时间（秒）
  const currentTime = (Date.now() - startTime) / 1000.0
  // 设置时间uniform
  const u_Time = gl.value.getUniformLocation(shaderProgram.value, 'u_Time')
  gl.value.uniform1f(u_Time, currentTime)

  // 设置分辨率uniform
  const u_Resolution = gl.value.getUniformLocation(shaderProgram.value, 'u_Resolution')
  gl.value.uniform2f(u_Resolution, demoCanvas.value.width, demoCanvas.value.height)

  // 清除画布
  gl.value.clearColor(0.0, 0.0, 0.0, 1.0)
  gl.value.clear(gl.value.COLOR_BUFFER_BIT)

  // 绘制两个三角形（矩形）
  gl.value.drawArrays(gl.value.TRIANGLE_STRIP, 0, 4)

  // 请求下一帧动画
  animationFrameId.value = requestAnimationFrame(draw)
}

// 初始化WebGL
const initWebGL = () => {
  if (!demoCanvas.value) return

  // 获取WebGL上下文
  gl.value = demoCanvas.value.getContext('webgl')
  if (!gl.value) {
    console.error('Failed to get WebGL context')
    return
  }

  // 创建WebGL工具实例
  webglUtils.value = new WebGLUtils(gl.value)

  // 顶点着色器源码
  const vertexShaderSource = `
    attribute vec4 a_Position;
    void main() {
      gl_Position = a_Position;
    }
  `

  // 简化后的片元着色器源码
  const fragmentShaderSource = `
    // 设置浮点数精度为中等
    precision mediump float;
    uniform vec2 u_Resolution;    // 画布分辨率
    uniform float u_Time;         // 当前时间

    void main() {
      // 将像素坐标归一化到[0,1]范围
      vec2 fragCoord = gl_FragCoord.xy / u_Resolution;

      // 计算宽高比
      float aspect = u_Resolution.x / u_Resolution.y;

      // 声明衰减因子和其他变量
      float attenuation;
      vec2 resolution = u_Resolution;

      // 将坐标系转换到[-1,1]范围，并进行Y轴翻转和宽高比校正
      vec2 centeredCoords = vec2((fragCoord.x * 2.0 - 1.0) * aspect, -(fragCoord.y * 2.0 - 1.0))/.7;

      // 创建倾斜效果的向量
      vec2 diagonalVector = vec2(-1.0, 1.0);

      // 黑洞中心点的坐标
      vec2 blackholeCenter = centeredCoords - 0.2 * diagonalVector;

      // 计算旋转坐标，应用透视畸变
      vec2 rotatedCoords = centeredCoords * mat2(
        1.0, 1.0,  // 基础旋转矩阵
        diagonalVector / (0.1 + 0.2 / dot(blackholeCenter, blackholeCenter)) // 透视畸变
      );

      // 动画控制变量
      float ii = 0.2;

      // 计算螺旋坐标，创造旋转效果
      vec2 spiralingCoords = rotatedCoords * mat2(
        cos(0.5 * log(attenuation = dot(rotatedCoords, rotatedCoords))
        + u_Time * ii
        + vec4(0.0, 33.0, 11.0, 0.0)) // 初始旋转角度
      ) / 0.2;

      // 初始化波浪累积值
      vec2 wavesCumulativeTotal = vec2(0.0);

      // 创建波浪效果
      for(int i = 0; i < 9; i++) {
        float iterValue = float(i) + 1.0;
        ii += 1.0;
        // 累积波浪值
        wavesCumulativeTotal += 1.0 + sin(spiralingCoords);
        // 扭曲坐标，创造动态波浪
        spiralingCoords += 0.7 * sin(spiralingCoords.yx * ii + u_Time) / ii + 0.5;
      }

      // 计算吸积盘半径
      float radius = length(
        sin(spiralingCoords / 0.3) * 0.4 +
        rotatedCoords * (3.0 + diagonalVector)
      );

      // 计算最终颜色
      vec4 outputColor = 1.0 - exp(-exp(rotatedCoords.x * vec4(.6, -.4, -1, 0))
                           / wavesCumulativeTotal.xyyx     // 应用波浪效果
                           / (2.0 + radius * radius / 4.0 - radius) // 吸积盘亮度
                           / (0.5 + 1.0 / attenuation)     // 中心暗度
                           / (0.03 + abs(length(centeredCoords) - 0.6)) // 边缘高光
                          );

      // 输出最终颜色
      gl_FragColor = outputColor;
    }
  `

  // 创建着色器
  const vertexShader = webglUtils.value.createShader(gl.value.VERTEX_SHADER, vertexShaderSource)
  const fragmentShader = webglUtils.value.createShader(gl.value.FRAGMENT_SHADER, fragmentShaderSource)
  if (!vertexShader || !fragmentShader) return

  // 创建程序并链接着色器
  shaderProgram.value = webglUtils.value.createProgram(vertexShader, fragmentShader)
  if (!shaderProgram.value) return

  gl.value.useProgram(shaderProgram.value)

  // 初始化顶点缓冲区
  if (!initVertexBuffers()) return

  // 记录开始时间
  startTime = Date.now()

  // 开始绘制
  draw()
}

// 组件挂载时初始化WebGL
onMounted(() => {
  initWebGL()
})

// 组件卸载前清理资源
onBeforeUnmount(() => {
  if (animationFrameId.value) {
    cancelAnimationFrame(animationFrameId.value)
  }
})
</script>

<style lang="scss" scoped>
.webgl-blackhole {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;

  h1 {
    color: #303133;
    margin-bottom: 20px;
  }

  p {
    margin-bottom: 30px;
  }

  .demo-section,
  .code-section,
  .info-section {
    margin: 30px 0;

    .card-header {
      display: flex;
      align-items: center;

      h3 {
        margin: 0;
        color: #409EFF;
      }
    }
  }

  .demo-section {
    .demo-content {
      display: flex;
      justify-content: center;

      canvas {
        border: 1px solid #dcdfe6;
        background-color: #000;
      }
    }
  }

  .code-section,
  .info-section {
    .code-content {
      background-color: #f8f9fa;
      padding: 15px;
      border-radius: 4px;

      code {
        font-family: monospace;
        color: #409EFF;
      }

      pre {
        margin: 0;
        white-space: pre-wrap;
      }
    }
  }
}
</style>
